﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Migrator.Core;
using Migrator.Util;
using Migrator.Entities;
using Migrator.Packets;
using MigratorUI.Replayer.Commands;
using MigratorUI.Replayer;
using MigratorUI.RenderEngine;
using System.Reflection;
using System.Windows.Shapes;
using Migrator.Events;
using Migrator.Exceptions;
using Migrator.Services.Advertisement.Packets;
using Migrator.Services.DHCP.Packets;

namespace MigratorUI.UI
{
    public partial class MigratorForm : Form
    {
        #region Construtor
        public MigratorForm()
        {
            InitializeComponent();
            

            this.wpfHost.AllowDrop = true;
            this.scene = new Scene();
            this.wpfHost.Child = this.scene;
            this.simulator = Simulator.Instance;
            this.scene.Background = System.Windows.Media.Brushes.Chocolate;

            this.menuItemExternalMoveDetection.Checked = true;
            this.routeOptimizationToolStripMenuItem.Checked = false;

            //events 
            this.scene.SpriteAttachedEvent += new Scene.SpriteAttachedEventHandler(SpriteAttachedHandler);
            this.scene.SpriteRemovedEvent += new Scene.SpriteRemovedEventHandler(SpriteRemovedHandler);
            this.scene.SpriteMovedEvent += new Scene.SpriteMovedEventHandler(SpriteMovedHandler);
            this.scene.SpriteSelectedEvent += new Scene.SpriteSelectedEventHandler(SpriteSelectedHandler);
            this.scene.ViewPacketsReceivedEvent += new Scene.ViewPacketsEventHandler(ViewPacketsReceivedHandler);
            this.scene.ViewPacketsSentEvent += new Scene.ViewPacketsEventHandler(ViewPacketsSentHandler);
            this.scene.SendPacketsEvent += new Scene.SendPacketsEventHandler(SendPacteksHandler);
            this.simulator.Internet.PacketSent += new Node.PacketSentEventHandler(NodePacketSentHandler);
            this.simulator.Internet.PacketReceived += new Node.PacketReceivedEventHandler(NodePacketReceivedHandler);
            this.simulator.Internet.PacketSent += new Node.PacketSentEventHandler(AddPacketToSentTable);
            this.simulator.Internet.PacketReceived += new Node.PacketReceivedEventHandler(AddPacketToReceivedTable);
            this.routerProperitiesPanel.PropertyChangedEvent += new NodePropertiesControl.PropertyChangedEventHandler(PropertyChangedHandler);
            this.laptopPropertiesPanel.PropertyChangedEvent += new LaptopPropertiesControl.PropertyChangedEventHandler(PropertyChangedHandler);
            TestScenario();
        }
        #endregion

        #region Internal methods

        private void CreateRouter(Sprite sprite)
        {
            RouterOptionsForm form = new RouterOptionsForm(this.simulator.Internet);
            
            if (form.ShowDialog() == DialogResult.OK)
            {
                MACAddress macAddress = new MACAddress(form.MACAddress);
                IPAddress ipAddress = new IPAddress(form.IPAddress);
                System.Drawing.PointF position = new System.Drawing.PointF((float)(sprite.Position.X - sprite.Image.Width / 2), (float)(sprite.Position.Y - sprite.Image.Height / 2));
                sprite.Id = macAddress.ToString();
                sprite.Radius = Migrator.Properties.Settings.Default.DefaultRadius;
                this.AddRouter(macAddress, ipAddress, position);
                this.scene.AttachAnimation(new ActiveAnimation(sprite,true));
            }

        }        

      
        private void CreateLaptop(Sprite sprite)
        {
            System.Drawing.PointF position = new System.Drawing.PointF((float)(sprite.Position.X - sprite.Image.Width / 2), (float)(sprite.Position.Y - sprite.Image.Height / 2));

            List<Router> routersInRange = this.simulator.Internet.GetRoutersInRange(position);

            if (routersInRange.Count > 0)
            {
                LaptopOptionsForm form = new LaptopOptionsForm(this.simulator.Internet, routersInRange);

                if (form.ShowDialog() == DialogResult.OK)
                {
                    MACAddress macAddress = new MACAddress(form.MACAddress);
                    sprite.Id = macAddress.ToString();
                    sprite.Radius = Migrator.Properties.Settings.Default.DefaultRadius;
                    this.AddLaptop(macAddress, position);
                    this.scene.AttachAnimation(new ActiveAnimation(sprite, true));
                }
            }
            else
            {
                this.scene.RemoveSprite(sprite);
                MessageBox.Show("Esta localização está fora do alcance de qualquer router.", "Erro ao adicionar portátil", MessageBoxButtons.OK, MessageBoxIcon.Error);

            }
        }

        private void AddRouter(MACAddress mac, IPAddress ip,PointF location)
        {
             Router router = new Router(mac, ip, this.simulator.Internet, location);

             router.PacketSent += new Node.PacketSentEventHandler(NodePacketSentHandler);
             router.PacketReceived += new Node.PacketReceivedEventHandler(NodePacketReceivedHandler);
             router.LocationChanged += new Node.LocationChangedEventHandler(NodeLocationChanged);
             router.PacketSent += new Node.PacketSentEventHandler(AddPacketToSentTable);
             router.PacketReceived += new Node.PacketReceivedEventHandler(AddPacketToReceivedTable);
        }

        private void AddLaptop(MACAddress mac, PointF location)
        {
            MobileNode laptop = new MobileNode(mac, IPAddress.NullAddress, this.simulator.Internet, location);
            laptop.PacketSent += new Node.PacketSentEventHandler(NodePacketSentHandler);
            laptop.PacketReceived += new Node.PacketReceivedEventHandler(NodePacketReceivedHandler);
            laptop.LocationChanged += new Node.LocationChangedEventHandler(NodeLocationChanged);

            laptop.PacketSent += new Node.PacketSentEventHandler(AddPacketToSentTable);
            laptop.PacketReceived += new Node.PacketReceivedEventHandler(AddPacketToReceivedTable);
        }           

        private void TestScenario()
        {
            // à pedreiro

            this.scene.SpriteAttachedEvent -= this.SpriteAttachedHandler;


            // Router 1
            MACAddress mac = new MACAddress("00:00:00:00:00:01");
            IPAddress ip = new IPAddress("192.168.1.1");
            PointF position = new PointF(100, 100);

            this.AddRouter(mac, ip, position);

            Sprite routerSprite = new Sprite(mac.ToString(), global::MigratorUI.Properties.Resources.Router, true, 100);
            scene.AttachSprite(routerSprite, new System.Windows.Point(position.X, position.Y));
            this.scene.AttachAnimation(new ActiveAnimation(routerSprite, true));

            // Router 2
            mac = new MACAddress("00:01:00:00:00:01");
            ip = new IPAddress("10.0.0.1");
            position = new PointF(260, 260);

            this.AddRouter(mac, ip, position);

            routerSprite = new Sprite(mac.ToString(), global::MigratorUI.Properties.Resources.Router, true, 100);
            scene.AttachSprite(routerSprite, new System.Windows.Point(position.X, position.Y));
            this.scene.AttachAnimation(new ActiveAnimation(routerSprite, true));

            // Router 3
            mac = new MACAddress("40:01:00:00:00:01");
            ip = new IPAddress("1.0.0.1");
            position = new PointF(460, 100);

            this.AddRouter(mac, ip, position);

            routerSprite = new Sprite(mac.ToString(), global::MigratorUI.Properties.Resources.Router, true, 100);
            scene.AttachSprite(routerSprite, new System.Windows.Point(position.X, position.Y));
            this.scene.AttachAnimation(new ActiveAnimation(routerSprite, true));

            // Laptop 1

            // Router 2
            mac = new MACAddress("00:02:00:00:00:01");
            position = new PointF(200, 200);

            this.AddLaptop(mac, position);

            Sprite laptopSprite = new Sprite(mac.ToString(), global::MigratorUI.Properties.Resources.Laptop, true, 100);
            scene.AttachSprite(laptopSprite, new System.Windows.Point(position.X, position.Y));
            this.scene.AttachAnimation(new ActiveAnimation(laptopSprite, true));

            

            mac = new MACAddress("00:72:00:00:00:01");
            position = new PointF(490, 150);

            this.AddLaptop(mac, position);

            laptopSprite = new Sprite(mac.ToString(), global::MigratorUI.Properties.Resources.Laptop, true, 100);
            scene.AttachSprite(laptopSprite, new System.Windows.Point(position.X, position.Y));
            this.scene.AttachAnimation(new ActiveAnimation(laptopSprite, true));

            this.scene.SpriteAttachedEvent += this.SpriteAttachedHandler;

            this.scene.SpriteAttachedEvent += this.SpriteAttachedHandler;
              //this.AddRouter(new MACAddress("00:00:00:00:00:03"), new IPAddress("10.0.0.1"), new PointF(260, 260));
              //this.AddLaptop(new MACAddress("00:00:00:00:00:02"), new PointF(160, 160));
           
           // this.AddLaptop(new MACAddress("00:00:00:00:00:04"), new PointF(300, 300));
           // this.AddLaptop(new MACAddress("00:00:00:00:00:05"), new PointF(170, 170));
        }

        #endregion
        

        #region Event Handlers
        
        /// <summary>
        /// Play Button Click Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPlayButtonClick(object sender, EventArgs e)
        {
            this.simulator.Step();
            this.statusLabel.Text = "Step "+this.simulator.Clock;
            actionSaver.AddCommand(new StepCommand());
        }

        /// <summary>
        /// Reply Button Click Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnReplyButtonClick(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Simulation files (*.sim)|*.sim|All files (*.*)|*.*";

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                string filename = openFileDialog.FileName;
                var replayer = new Replayer.Replayer(filename);
                replayer.ReplaySimulation();
            }

        }

        /// <summary>
        /// Add Router Button Click Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAddRouterButtonMouseMoveDown(object sender, MouseEventArgs e)
        {
            this.spriteItemType = SpriteItemType.Router;
            Random rand = new Random();
            System.Windows.DataObject sprite = new System.Windows.DataObject(Sprite.spriteFormat.Name, new Sprite("Sprite"+rand.Next(0,1000), global::MigratorUI.Properties.Resources.Router, true, 70));
            System.Windows.DragDrop.DoDragDrop(this.scene, sprite, System.Windows.DragDropEffects.Copy);
            
        }

        /// <summary>
        /// Add LapTop Button Click Handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAddLaptopButtonMouseMoveDown(object sender, MouseEventArgs e)
        {
            this.spriteItemType = SpriteItemType.Laptop;
            Random rand = new Random();
            System.Windows.DataObject sprite = new System.Windows.DataObject(Sprite.spriteFormat.Name, new Sprite("Sprite" + rand.Next(0, 1000), global::MigratorUI.Properties.Resources.Laptop, true, 70));
            System.Windows.DragDrop.DoDragDrop(this.scene, sprite, System.Windows.DragDropEffects.Copy);
        }

        /// <summary>
        /// Sprite Attached Handler
        /// </summary>
        /// <param name="sprite"></param>
        private void SpriteAttachedHandler(Sprite sprite)
        {
            if(this.spriteItemType == SpriteItemType.Router)
                CreateRouter(sprite);
            else if (this.spriteItemType == SpriteItemType.Laptop)
                CreateLaptop(sprite);
        }

        /// <summary>
        /// Sprite Removed Handler
        /// </summary>
        /// <param name="sprite"></param>
        private void SpriteRemovedHandler(Sprite sprite)
        {
          //Implemetar pelo core para remover um router ou laptop do core
        }

        /// <summary>
        /// Sprite Moved Handler
        /// </summary>
        /// <param name="sprite"></param>
        /// <param name="newPosition"></param>
        private void SpriteMovedHandler(Sprite sprite, System.Windows.Point newPosition)
        {
            try
            {
                Node node = this.simulator.Internet[new MACAddress(sprite.Id)];
                node.Location = new PointF((float)newPosition.X, (float)newPosition.Y);
                actionSaver.AddCommand(new MoveAgentCommand(node.MACAddress, node.Location));
            }
            catch (InvalidAddressException) {
                this.scene.RemoveSprite(sprite);
            }
        }

        /// <summary>
        /// Packet Sent Handler
        /// </summary>
        /// <param name="node"></param>
        /// <param name="e"></param>
        private void NodePacketSentHandler(Node node, PacketSentEventArgs e)
        {
           

            if (this.scene.Sprites.ContainsKey(node.MACAddress.ToString())){
                
                if (e.Packet.DestinationIP == IPAddress.BroadcastAddress) {
                    BroadCastAnimation animation = new BroadCastAnimation(this.scene.Sprites[node.MACAddress.ToString()], false);
                    this.scene.AttachAnimation(animation);
                }
            
                this.spriteItemType = SpriteItemType.Packet;
                //Create Sprite 
                String id = node.MACAddress.ToString() + e.Destination.MACAddress.ToString()+ e.Packet.GetHashCode();
                Sprite sprite = new Sprite(id, GetPacketBitmap(e.Packet), false, 0);
                this.scene.AttachSprite(sprite, this.scene.Sprites[node.MACAddress.ToString()].Position);
                this.spriteItemType = SpriteItemType.Packet;

                Console.WriteLine("### Criado pacote " + e.Packet + " vindo de " + node);

                //verify destination 
                if (this.scene.Sprites.ContainsKey(e.Destination.MACAddress.ToString()))
                {
                    System.Windows.Point destinationPosition = this.scene.Sprites[e.Destination.MACAddress.ToString()].Position;

                    //Create Animation
                    this.scene.MoveSpriteTo(sprite,destinationPosition);
                }
            }
        }

        private Bitmap GetPacketBitmap(IPPacket packet)
        {
            if (packet is MobileIPAdvertisementPacket)
                return global::MigratorUI.Properties.Resources.PacketAdvertisement;
            if (packet is DHCPDiscoverPacket)
                return global::MigratorUI.Properties.Resources.PacketDHCP;

            return global::MigratorUI.Properties.Resources.Packet;
        }

        /// <summary>
        /// Packet Received Handler
        /// </summary>
        /// <param name="node"></param>
        /// <param name="packet"></param>
        private void NodePacketReceivedHandler(Node node, IPPacket packet)
        {
            String id = packet.Hops.Last().MACAddress.ToString() + node.MACAddress.ToString() + packet.GetHashCode();

            if (this.scene.Sprites.ContainsKey(id)){
                Console.WriteLine("### Removido pacote " + packet + " recebido em " + node);

                this.scene.RemoveSprite(this.scene.Sprites[id]);
            }
        }

        /// <summary>
        /// Handler for Replayer Mode
        /// </summary>
        /// <param name="source"></param>
        /// <param name="oldLocation"></param>
        void NodeLocationChanged(Node source, PointF oldLocation)
        {

            if (this.scene.Sprites.ContainsKey(source.MACAddress.ToString()))
            {
                Sprite sprite = this.scene.Sprites[source.MACAddress.ToString()];
                this.scene.MoveSpriteTo(sprite, new System.Windows.Point(source.Location.X + sprite.Image.Width / 2, source.Location.Y + sprite.Image.Height / 2));
            }

        }

        /// <summary>
        /// On check or uncheck External Move Detection option
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMenuItemExternalMoveDetectionCheckChange(object sender, EventArgs e)
        {
            Migrator.Properties.Settings.Default.WillRegisterOnMove = this.menuItemExternalMoveDetection.CheckState == CheckState.Checked;
        }

        private void OnMenuItemExternalMoveDetectionClick(object sender, EventArgs e)
        {
            this.menuItemExternalMoveDetection.Checked = !this.menuItemExternalMoveDetection.Checked;
        }

        /// <summary>
        /// On check or uncheck Route Optimization
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMenuItemRouteOptimizationCheckChange(object sender, EventArgs e)
        {
            Migrator.Properties.Settings.Default.EnableRouteOptimization = this.routeOptimizationToolStripMenuItem.Checked;
        }

        private void OnMenuItemRouteOptimizationClick(object sender, EventArgs e)
        {
            this.routeOptimizationToolStripMenuItem.Checked = !this.routeOptimizationToolStripMenuItem.Checked;
        }

        /// <summary>
        /// On Select an node
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SpriteSelectedHandler(Sprite sprite)
        {
           Node node = this.simulator.Internet[new MACAddress(sprite.Id)];
           if (node != null){
               
               if (node is Router){
                   this.laptopPropertiesPanel.Hide();
                   this.routerProperitiesPanel.Show();
                   this.routerProperitiesPanel.LoadNode(node);
               }else{
                   this.routerProperitiesPanel.Hide();
                   this.laptopPropertiesPanel.Show();
                   this.laptopPropertiesPanel.LoadNode(node);
               }
           }
        }

        /// <summary>
        /// Handler for changed properties 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PropertyChangedHandler(Node node)
        {
            if (this.scene.Sprites.ContainsKey(node.MACAddress.ToString()))
            {
                Sprite sprite = this.scene.Sprites[node.MACAddress.ToString()];
                sprite.Radius = node.Radius;
                this.scene.UpdateSprite(sprite);
            }
        }

        /// <summary>
        /// Adds a packet to the received table 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="packet"></param>
        private void AddPacketToReceivedTable(Node source, IPPacket packet)
        {
            if (!packetsReceived.ContainsKey(source.MACAddress))
            {
                List<IPPacket> ipPackets = new List<IPPacket>();
                ipPackets.Add(packet);
                this.packetsReceived.Add(source.MACAddress, ipPackets);
            }
            else
            {
                packetsReceived[source.MACAddress].Add(packet);
            }
        }

        /// <summary>
        /// Adds a packet to the sent table 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="packet"></param>
        private void AddPacketToSentTable(Node source, PacketSentEventArgs args)
        {
            try
            {
                if (!packetsSent.ContainsKey(source.MACAddress))
                {
                    List<IPPacket> ipPackets = new List<IPPacket>();
                    ipPackets.Add(args.Packet);
                    this.packetsSent.Add(source.MACAddress, ipPackets);
                }
                else
                {
                    packetsSent[source.MACAddress].Add(args.Packet);
                }
            }
            catch (InvalidAddressException)
            {
            }
        }
        

        /// <summary>
        /// View Packets 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="packet"></param>
        private void ViewPacketsReceivedHandler(Sprite sprite)
        {
            try
            {
                Node node = this.simulator.Internet[new MACAddress(sprite.Id)];
                if (node != null)
                {
                    if (!packetsReceived.ContainsKey(node.MACAddress))
                        return;

                    NodePacketsForm form = new NodePacketsForm(node, packetsReceived[node.MACAddress], true);
                    form.Show();
                }
            }
            catch (InvalidAddressException)
            {
            }
        }

        /// <summary>
        /// View Packets  sent
        /// </summary>
        /// <param name="source"></param>
        /// <param name="packet"></param>
        private void ViewPacketsSentHandler(Sprite sprite)
        {
            Node node = this.simulator.Internet[new MACAddress(sprite.Id)];
            if (node != null)
            {
                if (!packetsSent.ContainsKey(node.MACAddress))
                    return;

                NodePacketsForm form = new NodePacketsForm(node, packetsSent[node.MACAddress], true);
                form.Show();
            }
        }

        /// <summary>
        /// Send Packets 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="packet"></param>
        private void SendPacteksHandler(Sprite sprite)
        {
            try
            {
                Node curNode = this.simulator.Internet[new MACAddress(sprite.Id)];
                if (curNode != null)
                {
                    var laptops = this.simulator.Internet.NetworkSelect(node => node).Where(node => (node.MACAddress != curNode.MACAddress));

                    SendPacketForm form = new SendPacketForm(laptops);
                    if (form.ShowDialog() == DialogResult.OK) {
                        IPPacket packet = new IPPacket(curNode, form.selectedIP);
                        curNode.Send(packet);
                    }
                }
            }
            catch (Exception)
            {
            }
           
        }
        #endregion

        //private 
        private Simulator simulator;
        private ActionSaver actionSaver = new ActionSaver(DateTime.Now.ToString().Replace(":", ".") + ".sim");
        private enum SpriteItemType { Router, Laptop,Packet };
        private SpriteItemType spriteItemType;
        private Scene scene;        
        private Dictionary<MACAddress, List<IPPacket>> packetsSent = new Dictionary<MACAddress, List<IPPacket>>();
        private Dictionary<MACAddress, List<IPPacket>> packetsReceived = new Dictionary<MACAddress, List<IPPacket>>();

    }

}
